package com.github.gaojh.aop;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import com.github.gaojh.aop.annotation.Aop;
import com.github.gaojh.aop.aspects.AopAdapter;
import com.github.gaojh.ioc.IocUtil;
import com.github.gaojh.ioc.bean.ClassInfo;
import com.github.gaojh.ioc.bean.ClassScanner;
import com.github.gaojh.ioc.bean.Initializer;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author gaojh
 * @since 2020/3/28
 */
@Slf4j
public class AopInitializer implements Initializer {

    @Override
    public void init(ClassScanner classScanner) throws Exception {
        List<ClassInfo> list = classScanner.getClassByAnnotation(Aop.class);
        if (CollectionUtil.isEmpty(list)) {
            return;
        }

        Stream<ClassInfo> stream = classScanner.getAllClass().stream();
        for (ClassInfo classInfo : list) {
            Class<?> clazz = classInfo.getBeanClass();
            Aop aop = clazz.getAnnotation(Aop.class);
            if (StrUtil.isBlank(aop.execution())) {
                return;
            }
            AopExecution aopExecution = new AopExecution();
            aopExecution.setExecution(aop.execution());
            AopAdapter aopAdapter = (AopAdapter) clazz.newInstance();

            String basePackage = aopExecution.getDeclaringTypePattern();
            if (basePackage != null) {
                stream = stream.filter(ci -> ReUtil.isMatch(basePackage, ci.getBeanClass().getPackage().getName()));
            }

            stream.filter(ci -> !ci.getBeanClass().isAnnotationPresent(Aop.class)).forEach(ci -> {
                List<Method> methods = Arrays.stream(ci.getBeanClass().getDeclaredMethods())
                        .filter(method -> ReUtil.isMatch(aopExecution.getNamePattern(), method.getName()))
                        .filter(method -> {
                            if (StrUtil.isBlank(aopExecution.getParamPattern())) {
                                return method.getParameterCount() == 0;
                            } else if (StrUtil.equals(aopExecution.getParamPattern(), "..")) {
                                return true;
                            } else {
                                String params = Stream.of(method.getParameterTypes()).map(Class::getName).collect(Collectors.joining(","));
                                return ReUtil.isMatch(aopExecution.getParamPattern(), params);
                            }
                        })
                        .filter(method -> ReUtil.isMatch(aopExecution.getRetTypePattern(), method.getReturnType().getName()))
                        .collect(Collectors.toList());

                if (CollectionUtil.isNotEmpty(methods)) {
                    AopInfo aopInfo = new AopInfo();
                    aopInfo.setAopAdapter(aopAdapter);
                    aopInfo.setClassInfo(ci);
                    aopInfo.setMethods(methods);
                    log.info("生成AOP代理类：{}", ci.getBeanName());
                    Object target = IocUtil.getBean(ci.getBeanName());
                    target = ProxyUtil.proxy(target, aopInfo);
                    IocUtil.addBean(ci.getBeanName(), target);
                }
            });

        }
    }

}
